package tichu_project252.Model;

import java.awt.Color;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.LinkedList;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.border.LineBorder;
import tichu_project252.CardClasses.Card;
import tichu_project252.CardClasses.simpleCard;
import tichu_project252.Deck;
import tichu_project252.Player;
import tichu_project252.Round.*;
import tichu_project252.Table;
import tichu_project252.View.Player.PlayerCards;
import tichu_project252.View.View;

/**
 *
 * This class implements the model of the game. The model represents data and
 * the rules that govern access to and updates of this data. In enterprise
 * software, a model often serves as a software approximation of a real-world
 * process.
 *
 * <p> <b> Invariants: </b> </p>
 *
 * @author Alexandra Barka, ΑΜ: 2867, login : barka.
 * @author Euaggelia Petauraki AM: 2875 login: petavraki.
 * @version %I%, %G%
 *
 */
public class Model {

    private String[] name = {"Player1", "Player2", "Player3", "Player4"}; // the names of the players
    private Player[] player;
    private Deck deck; //the deck of the game
    private Table table;
    private int currentPlayer; //who is current player
    private View view;
    private Score score;

    /**
     * Constructs a new model of the game.
     *
     * Creates the players
     *
     * <p><b>Constructor: </b>Model()</p>
     *
     * <p><b>Precondition: </b> </p> <p><b>Postcondition: </b> Constructs a new
     * model of the game </p>
     *
     * @throws Exception if a mistake occures
     */
    public Model() throws Exception {
        score = new Score();
        deck = new Deck();
        table = new Table();
        player = new Player[4];
        for (int i = 0; i < player.length; i++) {
            player[i] = new Player(name[i]);
        }

        this.deck.shuffle();

        this.deal(8);
        //Thread.sleep(4500); (to make the program stop for a few seconds to push grand tichu button)
        //this.deal(6);
    }

    /**
     * Deals the card to the player. First we deal 8 cards, and after 6.
     *
     * <p><b>Transformer(mutator):</b> deal(int i ) </p> <p><b>Precondition:</b>
     * The deck has been initialized</p> <p> <b>Postcondition</b> The deck is
     * dealt to players</p>
     *
     * @param i The number of the cards that being dealt
     * @throws Exception if something goes wrong
     */
    public void deal(int i) throws Exception {

        if (i != 8 && i != 6) {
            throw new Exception("Wrong.");
        } else {
            for (int n = 0; n < player.length * i; n++) {
                player[n % 4].add(deck.remove());
            }
        }
    }

    /**
     * Updates the Tichu variable of the player.
     */
    public void handleTichu() {
        this.player[this.currentPlayer % 4].setTichu(true);
    }

    /**
     * Updates the grand Tichu variable of the player.
     */
    public void handleGrandTichu() {
        this.player[this.currentPlayer % 4].setGrandTichu(true);
    }

    /**
     * Exits the game.
     *
     */
    public void handleExit() {
        System.exit(0);
    }

    /**
     * Start a new game.
     */
    public void handleNewGame() throws Exception {
        score = new Score();
        deck = new Deck();
        table = new Table();
        player = new Player[4];
        for (int i = 0; i < player.length; i++) {
            player[i] = new Player(name[i]);
        }

        this.deck.shuffle();

        this.deal(8);
        view.getFrame().setMessage("Game Starts!" + currentPlayer + " is the current player!");
    }

    /**
     * Passes the turn.
     */
    public boolean handlePass(int i) {


        player[i].setPass(true);

        int counter = 0;

        for (int p = 0; p < 4; p++) { //if someone said pass, increase the counter
            if (player[p].getPass()) {
                counter++;
            }
        }

        if (counter == 4) {
            for (int p = 0; p < 4; p++) { //if all said pass, return the setPass in false
                player[p].setPass(false);

            }
            return true;
        }
        this.currentPlayer++;
        view.getFrame().setMessage("Player " + i + " passed!");
        return false;

    }

    /**
     * Chooses cards.
     */
    public void handleChoose(PlayerCards p, Card c) {

        if (c.getSelected() == true) { // if the card is selected and we push it again, we deselected meaning that it will not have bold border
            p.setBorder(new LineBorder(Color.BLACK, 1, false));
            c.setSelected(false);
        } else { //else if we select one unselected card it will have a bold border.
            p.setBorder(new LineBorder(Color.BLUE, 5, false));
            c.setSelected(true);
        }

    }

    /**
     * Play a bomb.
     */
    private boolean isBomb(LinkedList<Card> temp) {
        //normal bomb
        if (temp.size() == 4) {
            //all cards must be the same
            int repr = temp.get(0).getintRepr();
            for (int i = 0; i < temp.size(); i++) {
                if (temp.get(i).getintRepr() != repr) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

    private boolean isStreetBomb(LinkedList<Card> temp) {
        if (temp.size() >= 5) {
            if (Street.isFlush(temp, table, true)) {
                return true;
            }
        }
        return false;
    }

    public boolean checkScore() {
        boolean flag = false;

        if (this.activePlayers() < 2) {
            if (player[0].isActive() && player[2].isActive()) {
                flag = true;
            }
            if (player[1].isActive() && player[3].isActive()) {
                flag = true;
            }
        } else if (this.activePlayers() < 1) {
            flag = true;
        }

        if (flag) {
            ScoringRound s = new ScoringRound();

            for (int i = 0; i < this.player.length; i++) {
                s.update(i, player[i].calcScore());
            }
            this.score.addScore(s);
        }
        return flag;
    }

    public int getScore(boolean team) {
        //score.calculate();
        if (team) {
            System.out.println(this.player[0].calcScore() + this.player[2].calcScore());
            return this.player[0].calcScore() + this.player[2].calcScore();
        } else {
            System.out.println(this.player[1].calcScore() + this.player[3].calcScore());
            return this.player[1].calcScore() + this.player[3].calcScore();
        }
    }

    public boolean handleBomb(int n) {
        LinkedList<Card> temp = player[n].playCards(false);
        boolean flag = false;

        //if it's a normal bomb
        if (isBomb(temp)) {
            //check if there's a bomb on the table already
            if (isBomb(table.getLastDropped())) {
                //if the bomb already down is less powerful, play it
                if (temp.get(0).getintRepr() > table.getLastDropped().get(0).getintRepr()) {
                    flag = true;
                    //else we're out
                } else {
                    return false;
                }
                //if there's not a bomb down, play the bomb
            } else {
                flag = true;
            }
            //if it's a street bomb
        } else if (isStreetBomb(temp)) {
            flag = true;
        } else {
            return false;
        }

        //play the cards
        if (flag) {
            this.currentPlayer = n;
            player[n].playCards(true);
            table.addCards(temp);

            if (activePlayers() == 3) {
                //the first player that finished his cards
                player[n].setFirst();
            }
            this.currentPlayer++;
        }

        return flag;
    }

    /**
     * Plays the cards that are selected.
     */
    public boolean handlePlay(int n) throws FileNotFoundException, IOException, LineUnavailableException, UnsupportedAudioFileException {

        LinkedList temp = player[n].playCards(false);
        System.out.println(temp);
        boolean flag = false;
        String s = new String();
        if ((player[n].hasHund()) && "Hund".equals(player[n].playCards(false).get(0).getstringRepr()) && (table.getLastDropped().size() == 0)) {
            s += "Hund!";
            this.currentPlayer++;
            flag = true;

        } else if (singleCard.isSingleCard(temp, table)) {
            s += "Single Card " + temp.size() + " ";
            flag = true;
        } else if (Pair.isPair(temp, table)) {
            s += "Pairs ";
            flag = true;
        } else if (Triple.isTriple(temp, table)) {
            s += "Triple ";
            flag = true;
        } else if (ascendingPairs.isascendingPairs(temp, table)) {
            s += "Ascending Pairs ";
            flag = true;
        } else if (fullHouse.isfullHouse(temp, table)) {
            s += "Full House ";
            flag = true;
        } else if (Street.isStreet(temp, table, false)) {
            s += "Street ";
            flag = true;
        } else {
            s += "Invalid Combination. ";
            flag = false;
        }

        if (flag == true) {
            System.out.println(temp.toString());
            player[n].playCards(true);
            table.addCards(temp);

            if (activePlayers() == 3) {
                //the first player that finished his cards
                player[n].setFirst();
            }

            this.currentPlayer++; // ama einai swstos o sundiasmos pigaine ston epomeno
        }

        s += " How many cards: " + temp.size() + " / " + player[n].getCards().size();
        s += "current player:" + currentPlayer;
        view.getFrame().setMessage(s);


        return flag;
    }

    /**
     * Updates the view.
     *
     * <p><b>Transformer(mutator):</b> setView(View v) </p>
     * <p><b>Precondition:</b> View has been initialized</p> <p>
     * <b>Postcondition</b> The model's view reference is initialized.</p>
     *
     * @param v
     */
    public void setView(View v) {           //allages apo to model sto view
        this.view = v;
    }

    private int activePlayers() {

        int active = 0;
        for (int i = 0; i < player.length; i++) {
            if (player[i].isActive()) {
                active = active + 1;
            }
        }
        return active;
    }

    public Player getPlayer(int i) {
        return player[i % 4];
    }

    public Table getTable() {
        return table;
    }

    public int getCurrent() {
        return this.currentPlayer;
    }

    public void setCurrent() {
        for (int i = 0; i < player.length; i++) {
            if (player[i].hasMahjong()) {
                this.currentPlayer = i;
            }
        }
    }

    class Score {

        LinkedList<ScoringRound> score = null;

        public Score() {
            score = new LinkedList<ScoringRound>();
        }

        public void addScore(ScoringRound s) {
            this.score.add(s);
            calculate();
        }

        public void calculate() {
            int current = score.size() - 1;

            if (score.get(current).max() == 0
                    && score.get(current - 1).max() == 2) {
                score.get(current).update(0, 200);
            }
            if (score.get(current).max() == 2
                    && score.get(current - 1).max() == 0) {
                score.get(current).update(2, 200);
            }
            if (score.get(current).max() == 1
                    && score.get(current - 1).max() == 3) {
                score.get(current).update(1, 200);
            }
            if (score.get(current).max() == 3
                    && score.get(current - 1).max() == 1) {
                score.get(current).update(3, 200);
            }
        }
    }
}

class ScoringRound {

    int score[] = null;

    public ScoringRound() {
        score = new int[4];
    }

    public void update(int p, int value) {
        score[p] = value;
    }

    public int max() {
        int max = score[0];
        int pos = 0;

        for (int i = 1; i < 4; i++) {
            if (max < score[i]) {
                max = score[i];
                pos = i;
            }
        }
        return pos;
    }
}